/* * Copyright (C) 2005-2012 BetaCONCEPT Limited * * This file is part of Astroboa. * * Astroboa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Astroboa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Astroboa. If not, see <http://www.gnu.org/licenses/>. */ package org.betaconceptframework.astroboa.engine.jcr.dao; import java.io.File; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.acl.Group; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.jcr.Repository; import javax.jcr.SimpleCredentials; import javax.security.auth.Subject; import javax.security.auth.login.AccountExpiredException; import javax.security.auth.login.AccountLockedException; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.CredentialExpiredException; import javax.security.auth.login.CredentialNotFoundException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.betaconceptframework.astroboa.api.model.CmsRepository; import org.betaconceptframework.astroboa.api.model.RepositoryUser; import org.betaconceptframework.astroboa.api.model.exception.CmsException; import org.betaconceptframework.astroboa.api.security.AstroboaCredentials; import org.betaconceptframework.astroboa.api.security.AstroboaPrincipalName; import org.betaconceptframework.astroboa.api.security.CmsRole; import org.betaconceptframework.astroboa.api.security.IdentityPrincipal; import org.betaconceptframework.astroboa.api.security.RepositoryUserIdPrincipal; import org.betaconceptframework.astroboa.api.security.exception.CmsInvalidPasswordException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginAccountExpiredException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginAccountLockedException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidCredentialsException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidPermanentKeyException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginInvalidUsernameException; import org.betaconceptframework.astroboa.api.security.exception.CmsLoginPasswordExpiredException; import org.betaconceptframework.astroboa.api.security.exception.CmsUnauthorizedRepositoryUseException; import org.betaconceptframework.astroboa.api.security.management.IdentityStore; import org.betaconceptframework.astroboa.api.security.management.IdentityStoreContextHolder; import org.betaconceptframework.astroboa.api.service.RepositoryUserService; import org.betaconceptframework.astroboa.cache.region.DefinitionCacheRegion; import org.betaconceptframework.astroboa.configuration.LocalizationType.Label; import org.betaconceptframework.astroboa.configuration.RepositoryRegistry; import org.betaconceptframework.astroboa.configuration.RepositoryType; import org.betaconceptframework.astroboa.configuration.SecurityType.PermanentUserKeyList.PermanentUserKey; import org.betaconceptframework.astroboa.context.AstroboaClientContext; import org.betaconceptframework.astroboa.context.AstroboaClientContextHolder; import org.betaconceptframework.astroboa.context.CmsRepositoryImpl; import org.betaconceptframework.astroboa.context.RepositoryContext; import org.betaconceptframework.astroboa.context.SecurityContext; import org.betaconceptframework.astroboa.engine.definition.ContentDefinitionConfiguration; import org.betaconceptframework.astroboa.engine.jcr.initialization.CmsRepositoryInitializationManager; import org.betaconceptframework.astroboa.engine.jcr.util.JackrabbitDependentUtils; import org.betaconceptframework.astroboa.engine.service.security.AstroboaLogin; import org.betaconceptframework.astroboa.model.lazy.LazyLoader; import org.betaconceptframework.astroboa.security.CmsGroup; import org.betaconceptframework.astroboa.security.CmsPrincipal; import org.betaconceptframework.astroboa.security.CmsRoleAffiliationFactory; import org.betaconceptframework.astroboa.security.CredentialsCallbackHandler; import org.betaconceptframework.astroboa.util.CmsConstants; import org.betaconceptframework.astroboa.util.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.io.FileSystemResource; import org.springframework.extensions.jcr.JcrSessionFactory; import org.springframework.extensions.jcr.SessionFactory; import org.springframework.extensions.jcr.jackrabbit.RepositoryFactoryBean; /** * @author Gregory Chomatas (gchomatas@betaconcept.com) * @author Savvas Triantafyllou (striantafyllou@betaconcept.com) * */ public class RepositoryDao implements ApplicationListener{ @Autowired private SimpleCredentials betaConceptCredentials; @Autowired private CmsRepositoryInitializationManager cmsRepositoryInitializationManager; @Autowired private ContentDefinitionConfiguration contentDefinitionConfiguration; @Autowired //Used mainly when initializing IdentityStore repository private LazyLoader lazyLoader; @Autowired //Bound to service so that a new Transaction is created //as this is used when RepositoryUserPrincipal must be added to subject private RepositoryUserService repositoryUserService; @Autowired private DefinitionCacheRegion definitionCacheRegion; @Autowired private ConsistencyCheckerDao consistencyCheckerDao; @Autowired private IdentityStore identityStore; private final Logger logger = LoggerFactory.getLogger(RepositoryDao.class); private Map<String, Repository> jcrRepositories = new HashMap<String, Repository>(); private Map<String, CmsRepository> repositoryInfos = new HashMap<String, CmsRepository>(); private Map<String, SessionFactory> jcrSessionFactoriesPerRepository = new HashMap<String, SessionFactory>(); private void loadRepositoryFromConfiguration(String repositoryId) { if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){ logger.warn("Found no configuration for repository "+repositoryId); return; } try{ RepositoryType repositoryConfiguration = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId); //Create a CmsRepository instance which holds all configuration parameters CmsRepository cmsRepository = loadConfigurationParameters(repositoryId, repositoryConfiguration); //Create JcrSessionFactory SessionFactory jcrSessionFactory = createJcrSessionFactory(repositoryId, repositoryConfiguration.getRepositoryHomeDirectory()); jcrSessionFactoriesPerRepository.put(repositoryId, jcrSessionFactory); if (fullReloadRepository(repositoryId, repositoryConfiguration)){ if (! repositoryInfos.containsKey(repositoryId)){ repositoryInfos.put(repositoryId, cmsRepository); } //Initialize repository and load definition to cache SecurityContext securityContext = new SecurityContext(repositoryId, null, 30, null); RepositoryContext repositoryContext = new RepositoryContext(cmsRepository, securityContext); AstroboaClientContextHolder.registerClientContext(new AstroboaClientContext(repositoryContext, new LazyLoader(null, null, null, null)), true); try{ cmsRepositoryInitializationManager.initialize(cmsRepository); contentDefinitionConfiguration.loadDefinitionToCache(); if (repositoryConfiguration.isCheckConsistency() || RepositoryRegistry.INSTANCE.isConsistencyCheckEnabled()){ consistencyCheckerDao.performReferentialIntegrityCheck(); } } catch(CmsException e){ repositoryInfos.remove(repositoryId); jcrSessionFactoriesPerRepository.remove(repositoryId); AstroboaClientContextHolder.clearContext(); throw e; } AstroboaClientContextHolder.clearContext(); // Create unmanaged datastore if not there try{ File repositoryHomeDir = new File(cmsRepository.getRepositoryHomeDirectory()); if (repositoryHomeDir.exists()){ File unmanagedDataStoreDirectory = new File(repositoryHomeDir, CmsConstants.UNMANAGED_DATASTORE_DIR_NAME); if (!unmanagedDataStoreDirectory.exists()){ if (!unmanagedDataStoreDirectory.mkdir()){ throw new Exception("Could not create UnmanagedDataStore directory. File.mkdir() returned false"); } logger.debug("Created UnmanagedDataStore directory for repository {} in path {}",cmsRepository.getId(), unmanagedDataStoreDirectory.getAbsolutePath()); } } } catch(Error e){ logger.warn("Could not create UnmanagedDataStore directory", e); } } } catch(Exception e){ logger.error("",e); return; } } private CmsRepository loadConfigurationParameters(String repositoryId, RepositoryType repositoryConfiguration) { String serverAliasURL = repositoryConfiguration.getServerAliasURL(); String restfulApiBasePath = repositoryConfiguration.getRestfulApiBasePath(); String repositoryServerURL = StringUtils.isBlank(serverAliasURL) ? RepositoryRegistry.INSTANCE.getDefaultServerURL() : serverAliasURL; String externalIdentityStoreJndiName = repositoryConfiguration.getExternalIdentityStoreJndiName(); String repositoryIdentityStoreId = null; if (StringUtils.isBlank(externalIdentityStoreJndiName)){ repositoryIdentityStoreId = (repositoryConfiguration.getIdentityStoreRepositoryId() == null )? RepositoryRegistry.INSTANCE.getDefaultIdentityStoreId(): repositoryConfiguration.getIdentityStoreRepositoryId(); if (StringUtils.isBlank(repositoryIdentityStoreId)){ //Define the repository to be identity store for it self repositoryIdentityStoreId = repositoryId; } } //Localized Labels HashMap<String, String> localizedLabels = new HashMap<String, String>(); if (repositoryConfiguration.getLocalization() != null){ List<Label> localizedLabelList = repositoryConfiguration.getLocalization().getLabel(); for (Label localizedLabel : localizedLabelList){ localizedLabels.put(localizedLabel.getLang(), localizedLabel.getValue()); } } CmsRepository cmsRepository = new CmsRepositoryImpl(repositoryId, localizedLabels, repositoryConfiguration.getRepositoryHomeDirectory(), repositoryServerURL, restfulApiBasePath, repositoryIdentityStoreId, externalIdentityStoreJndiName, repositoryConfiguration.getSecurity().getSecretUserKeyList().getAdministratorSecretKey().getUserid()); return cmsRepository; } private SessionFactory createJcrSessionFactory(String repositoryId, String repositoryHomeDirectory) throws Exception { if (! jcrRepositories.containsKey(repositoryId)){ //create repository first RepositoryFactoryBean repositoryFactory = new RepositoryFactoryBean(); repositoryFactory.setHomeDir(new FileSystemResource(repositoryHomeDirectory)); repositoryFactory.setConfiguration(new FileSystemResource(repositoryHomeDirectory+File.separator+"repository.xml")); repositoryFactory.afterPropertiesSet(); // FileSystemResource configuration = new FileSystemResource(repositoryHomeDirectory+File.separator+"repository.xml"); // FileSystemResource homeDir = new FileSystemResource(repositoryHomeDirectory); // // RepositoryConfig repositoryConfig = RepositoryConfig.create(new InputSource(configuration.getInputStream()), homeDir.getFile().getAbsolutePath()); // // Repository jcrRepository = RepositoryImpl.create(repositoryConfig); Repository jcrRepository = repositoryFactory.getObject(); if (jcrRepository == null){ throw new CmsException("Unable to initialize repository "+ repositoryId + " located in "+ repositoryHomeDirectory); } jcrRepositories.put(repositoryId, jcrRepository); } //Create JcrSessionFactory For default workspace JcrSessionFactory jcrSessionFactory = new JcrSessionFactory(); jcrSessionFactory.setCredentials(betaConceptCredentials); jcrSessionFactory.setRepository((Repository)jcrRepositories.get(repositoryId)); //Call this method to force further initialization process jcrSessionFactory.afterPropertiesSet(); return jcrSessionFactory; } public List<CmsRepository> getAvailableCmsRepositories() { deployRepositoriesFoundInTheRepositoryRegistry(); return new ArrayList<CmsRepository>(repositoryInfos.values()); } private void deployRepositoriesFoundInTheRepositoryRegistry() { if (RepositoryRegistry.INSTANCE.configurationHasChanged() || repositoryInfos.isEmpty()){ RepositoryRegistry.INSTANCE.loadRepositoryConfigurations(); /* * Key is the the repository home directory for the JCR Repository, value is the repository id */ Map<String,String> jcrRepositoryHomeDirectoryPerRepository = new HashMap<String, String>(); Set<String> repositoryIdsToBeUndeployed = new HashSet<String>(); for (Map.Entry<String, RepositoryType> repositoryConfigurationEntry : RepositoryRegistry.INSTANCE.getConfigurationsPerRepositoryId().entrySet()){ String repositoryId = repositoryConfigurationEntry.getKey(); RepositoryType repositoryConfiguration = repositoryConfigurationEntry.getValue(); String currentJcrRepositoryHomeDirectory = repositoryConfiguration.getRepositoryHomeDirectory(); boolean initializeRepository = true; //Check for duplicate repository home directory if (jcrRepositoryHomeDirectoryPerRepository.containsKey(currentJcrRepositoryHomeDirectory)){ logger.warn("Repository Home Directory '{}' already defined for repository '{}'. Repository {} will not be loaded.", new Object[]{currentJcrRepositoryHomeDirectory, jcrRepositoryHomeDirectoryPerRepository.get(currentJcrRepositoryHomeDirectory), repositoryId}); repositoryIdsToBeUndeployed.add(repositoryId); initializeRepository = false; } if (initializeRepository){ loadRepositoryFromConfiguration(repositoryId); } } //Mark any repository which does not exist in the configuration as to be undeployed //This step is meaningful only when the configuration file is updated for (String deployedRepositoryId : repositoryInfos.keySet()){ if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(deployedRepositoryId)){ logger.warn("Repository {} does not belong to the configuration and therefore will be undeployed", deployedRepositoryId); repositoryIdsToBeUndeployed.add(deployedRepositoryId); } } //Undeploy any repository whose configuration is invalid for (String repositoyIdToBeUndeployed : repositoryIdsToBeUndeployed){ repositoryInfos.remove(repositoyIdToBeUndeployed); jcrSessionFactoriesPerRepository.remove(repositoyIdToBeUndeployed); jcrRepositories.remove(repositoyIdToBeUndeployed); //Clear caches definitionCacheRegion.clearCacheForRepository(repositoyIdToBeUndeployed); logger.warn("Successfully undeployed repository {}", repositoryIdsToBeUndeployed); } //Detect any obvious cycles between repository and identity store repositoryInfos detectCycleBetweenRepositoryAndIdentityStoreRepositories(); //Second pass to initialize any repository which is an identity store for another repository for (CmsRepository cmsRepository : repositoryInfos.values()){ initializeIdentityStoreForRepository(cmsRepository); } } } private boolean fullReloadRepository(String repositoryId, RepositoryType repositoryConfiguration) { //Reload repository if repository has not been loaded if (! repositoryInfos.containsKey(repositoryId)){ return true; } //Check if cache manager settings have changed Repository repository = jcrRepositories.get(repositoryId); return JackrabbitDependentUtils.cacheManagerSettingsHaveChanged(repositoryConfiguration, repository); } private void initializeIdentityStoreForRepository( CmsRepository cmsRepository) { if (StringUtils.isBlank(cmsRepository.getExternalIdentityStoreJNDIName())){ String identityStoreRepositoryId = cmsRepository.getIdentityStoreRepositoryId(); if (StringUtils.isBlank(identityStoreRepositoryId)){ throw new CmsException("No external IdentityStore JNDI has been provided nor an identity store repository id for repository "+ cmsRepository.getId()); } if (!repositoryInfos.containsKey(identityStoreRepositoryId)){ throw new CmsException("Found no repository with id "+identityStoreRepositoryId+".Cannot initialize identity store for repository "+ cmsRepository.getId()); } CmsRepository cmsRepositoryIdentityStore = repositoryInfos.get(identityStoreRepositoryId); Subject subject = new Subject(); subject.getPrincipals().add(new IdentityPrincipal(IdentityPrincipal.SYSTEM)); Group rolesPrincipal = new CmsGroup(AstroboaPrincipalName.Roles.toString()); for (CmsRole cmsRole : CmsRole.values()){ rolesPrincipal.addMember(new CmsPrincipal(CmsRoleAffiliationFactory.INSTANCE.getCmsRoleAffiliationForRepository(cmsRole, identityStoreRepositoryId))); } subject.getPrincipals().add(rolesPrincipal); SecurityContext securityContext = new SecurityContext(identityStoreRepositoryId, subject, 30, null); RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryIdentityStore, securityContext); AstroboaClientContextHolder.registerClientContext(new AstroboaClientContext(repositoryContext, lazyLoader), true); cmsRepositoryInitializationManager.initializeIdentityStore(cmsRepository.getId(), cmsRepositoryIdentityStore); AstroboaClientContextHolder.clearContext(); } } /* * A repository may refer to an external Identity Store or to another repository. * * At the latter case, the repository that serves as an identity store MUST * have ITSELF as an identity store. * */ private void detectCycleBetweenRepositoryAndIdentityStoreRepositories() { List<String> repositoryIdsToBeRemoved = new ArrayList<String>(); for (CmsRepository cmsRepository : repositoryInfos.values()){ if (StringUtils.isNotBlank(cmsRepository.getExternalIdentityStoreJNDIName())){ //We are only interested in repositoryInfos which refer to //another repository as identity store continue; } else{ if (cmsRepository.getIdentityStoreRepositoryId()==null){ throw new CmsException("No external IdentityStore JNDI has been provided nor an identity store repository id for repository "+ cmsRepository.getId()); } else{ if (!StringUtils.equals(cmsRepository.getId(), cmsRepository.getIdentityStoreRepositoryId())){ //Repository refers to another repository for identity store. //Check that this repository refers to itself as identity store CmsRepository identityStoreRepository = repositoryInfos.get(cmsRepository.getIdentityStoreRepositoryId()); if (StringUtils.isNotBlank(identityStoreRepository.getExternalIdentityStoreJNDIName())){ logger.warn("Repository "+ cmsRepository.getId() + " refers to repository "+ identityStoreRepository.getId() +" to be its identity store, but the latter refers to an external identity store " + " which is not accepted.Both repositoties will be removed"); repositoryIdsToBeRemoved.add(cmsRepository.getId()); repositoryIdsToBeRemoved.add(identityStoreRepository.getId()); } else { if (!StringUtils.equals(identityStoreRepository.getId(), identityStoreRepository.getIdentityStoreRepositoryId())){ logger.warn("Repository "+ cmsRepository.getId() + " refers to repository "+ identityStoreRepository.getId() +" to be its identity store, but the latter refers to another repository for" + " identity store ("+identityStoreRepository.getIdentityStoreRepositoryId()+") and not its self" + " which is not accepted.Both repositoties will be removed"); repositoryIdsToBeRemoved.add(cmsRepository.getId()); repositoryIdsToBeRemoved.add(identityStoreRepository.getId()); } } } } } } for (String repositoyToBeRemoved : repositoryIdsToBeRemoved){ repositoryInfos.remove(repositoyToBeRemoved); jcrSessionFactoriesPerRepository.remove(repositoyToBeRemoved); jcrRepositories.remove(repositoyToBeRemoved); } } public String login(String repositoryId, AstroboaCredentials credentials) { return login(repositoryId, credentials, null, null); } private boolean isPermanentKeyValidForUser(String repositoryId, String permanentKey, String userid) { if (StringUtils.isBlank(repositoryId) || StringUtils.isBlank(permanentKey) || StringUtils.isBlank(userid)){ return false; } if (!RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){ return false; } RepositoryType repositoryConfiguration = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId); //If no trusted key have been registered to configuration then all keys are accepted if (repositoryConfiguration.getSecurity() == null || repositoryConfiguration.getSecurity().getPermanentUserKeyList() == null || CollectionUtils.isEmpty(repositoryConfiguration.getSecurity().getPermanentUserKeyList().getPermanentUserKey())){ return false; } List<PermanentUserKey> permanentUserKeys = repositoryConfiguration.getSecurity().getPermanentUserKeyList().getPermanentUserKey(); for (PermanentUserKey permanentUserKey : permanentUserKeys){ if (StringUtils.equals(permanentUserKey.getKey(), permanentKey)){ List<String> userIds = Arrays.asList(StringUtils.split(permanentUserKey.getUserid(), ",")); return userIds.contains("*") || userIds.contains(userid); } } return false; } private SecurityContext authenticate(AstroboaCredentials credentials, String repositoryId, int currentAuthenticationTokenTimeout, String permanentKey ) { SecurityContext securityContext = null; Subject subject = null; try { CredentialsCallbackHandler callbackHandler = null; if (credentials != null){ callbackHandler = new CredentialsCallbackHandler(credentials); } IdentityStoreContextHolder.setActiveRepositoryId(repositoryId); AstroboaLogin astroboaLogin = new AstroboaLogin(callbackHandler, identityStore, this); subject = astroboaLogin.login(); } catch(AccountNotFoundException e){ throw new CmsLoginInvalidUsernameException(e); } catch(FailedLoginException e){ throw new CmsInvalidPasswordException(e); } catch(AccountLockedException e){ throw new CmsLoginAccountLockedException(e); } catch(AccountExpiredException e){ throw new CmsLoginAccountExpiredException(e); } catch(CredentialNotFoundException e){ throw new CmsInvalidPasswordException(e); } catch(CredentialExpiredException e){ throw new CmsLoginPasswordExpiredException(e); } catch (LoginException e) { throw new CmsException(e); } catch(CmsException e){ throw e; } catch(Throwable t){ throw new CmsException(t); } finally{ IdentityStoreContextHolder.clear(); } authorizeSubject(subject, repositoryId); try{ String authenticationToken = createAuthenticationToken(subject, repositoryId, permanentKey); securityContext = new SecurityContext(authenticationToken, subject, currentAuthenticationTokenTimeout, getAvailableRepositoryIds()); if (logger.isDebugEnabled()){ logger.debug("Successfull authentication: Token {} , Subject {} for Thread {}" , new Object[]{authenticationToken, subject, Thread.currentThread()}); } return securityContext; } catch (NoSuchAlgorithmException e) { throw new CmsException(e); } } private List<String> getAvailableRepositoryIds() { return new ArrayList<String>(RepositoryRegistry.INSTANCE.getConfigurationsPerRepositoryId().keySet()); } private String createAuthenticationToken(Subject subject, String repositoryId, String permanentKey) throws NoSuchAlgorithmException { //create authentication token StringBuilder stringToDigest = new StringBuilder(repositoryId); //Token contains the value provided in the UserPrincipal //provided in Subject. If none is found throw an exception if (subject == null){ throw new CmsException("No subject was found after login"); } String identity = retrieveIdentityFromSubject(subject); stringToDigest.append(identity); if (StringUtils.isBlank(permanentKey)){ stringToDigest.append(DateUtils.format(Calendar.getInstance())) .append(UUID.randomUUID()); } else{ if (StringUtils.isNotBlank(permanentKey) && ! isPermanentKeyValidForUser(repositoryId,permanentKey, identity)){ throw new CmsLoginInvalidPermanentKeyException("Invalid permanent key "+ permanentKey + " for user "+ identity + " in repository "+ repositoryId); } stringToDigest.append(permanentKey); } //According to documentation in Base64 class //Encoding is done as defined in RFC 2045 - 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies //Since this character set contains '+' and '/' chars , these characters must be replaced by '_' and '-' accordingly //so that this token can be used in URLs if necessary. Since decoding process is not an issue the above chars can be used //although they are not defined in the 64-character set. MessageDigest messageDigest = MessageDigest.getInstance("SHA-512"); return new String(Base64.encodeBase64(messageDigest.digest(stringToDigest.toString().getBytes()))).replaceAll("\\+", "_").replaceAll("/", "-"); } private String retrieveIdentityFromSubject(Subject subject) { if (subject == null || CollectionUtils.isEmpty(subject.getPrincipals(IdentityPrincipal.class))){ throw new CmsException("Could not find identity principal in subject "+subject+" Unable to create authentication token"); } //Retrieve the first one. Normally it should not have more than one return subject.getPrincipals(IdentityPrincipal.class).iterator().next().getName(); } private int retrieveAuthenticationTokenTimeoutForRepository(String repositoryId) { RepositoryType repositoryConfigurationEntry = RepositoryRegistry.INSTANCE.getRepositoryConfiguration(repositoryId); if (repositoryConfigurationEntry == null){ throw new CmsException("Could not find configuration entry for repository "+ repositoryId); } return repositoryConfigurationEntry.getAuthenticationTokenTimeout() == null ? RepositoryRegistry.INSTANCE.getDefaultAuthenticationTokenTimeout() : repositoryConfigurationEntry.getAuthenticationTokenTimeout(); } public SessionFactory getJcrSessionFactoryForAssociatedRepository() { String associatedRepositoryId = getAssociatedRepositoryId(); if (! jcrSessionFactoriesPerRepository.containsKey(associatedRepositoryId)){ if (!repositoryInfos.containsKey(associatedRepositoryId)){ loadRepositoryFromConfiguration(associatedRepositoryId); if (! jcrSessionFactoriesPerRepository.containsKey(associatedRepositoryId)){ throw new CmsException("Found no jcr session factory for associated repository "+ associatedRepositoryId); } } else{ throw new CmsException("Found no jcr session factory for associated repository "+ associatedRepositoryId); } } return jcrSessionFactoriesPerRepository.get(associatedRepositoryId); } private String getAssociatedRepositoryId() { String associatedRepositoryId = AstroboaClientContextHolder.getActiveRepositoryId(); if (associatedRepositoryId == null){ throw new CmsException("Found no repository context"); } return associatedRepositoryId; } public boolean isRepositoryAvailable(String repositoryId) { deployRepositoriesFoundInTheRepositoryRegistry(); return StringUtils.isNotBlank(repositoryId) && repositoryInfos.containsKey(repositoryId); } public CmsRepository getCurrentConnectedRepository() { return AstroboaClientContextHolder.getActiveClientContext() != null ? ( AstroboaClientContextHolder.getActiveClientContext().getRepositoryContext() != null ? AstroboaClientContextHolder.getActiveClientContext().getRepositoryContext().getCmsRepository() : null) : null; } public CmsRepository getCmsRepository(String repositoryId) { if (StringUtils.isBlank(repositoryId)){ return null; } if (!repositoryInfos.containsKey(repositoryId)){ deployRepositoriesFoundInTheRepositoryRegistry(); } return repositoryInfos.get(repositoryId); } @Override public void onApplicationEvent(ApplicationEvent event) { //Need to initialize all repositoryInfos defined in configuration xml //after Spring Context has been instantiated if (event instanceof ContextRefreshedEvent) { deployRepositoriesFoundInTheRepositoryRegistry(); } } private void addRepositoryUserIdPrincipalToSubject(Subject subject, String identity) { //Must provide a principal which will hold the RepositoryUserId //No need to check if user is ANONYMOUS if (StringUtils.isNotBlank(identity) && ! StringUtils.equals(identity, IdentityPrincipal.ANONYMOUS)){ RepositoryUser repositoryUser = repositoryUserService.getRepositoryUser(identity); if (repositoryUser != null && repositoryUser.getId() != null){ subject.getPrincipals().add(new RepositoryUserIdPrincipal(repositoryUser.getId())); } } } /** * Subject authorization at this level is restricted only to authorize user * whether she can or cannot login to the specified repository. * Our default policy is a PERMIT REPOSITORY policy, meaning that an authenticated user * has access to REPOSITORY available repositoryInfos defined within a Astroboa Server. * * In cases where an authenticated user has access to a subset of available repositoryInfos * then a {@link Group} named after "AuthorizedRepositories" must exist * among {@link Subject} principals. * * If so, only and only if the specified repository exists inside this list, * the user will be authorized to use Astroboa services for that repository. */ private void authorizeSubject(Subject subject, String repositoryId) { if (subject == null){ throw new CmsException("No subject provided "); } //In case authenticated Set<Principal> principals = subject.getPrincipals(); if (CollectionUtils.isNotEmpty(principals)){ for (Principal principal : principals){ if (principal instanceof Group && AstroboaPrincipalName.AuthorizedRepositories.toString().equals(principal.getName()) ){ //Found authorized repositoryInfos boolean userIsAuthorizedToAccessRepository = false; for (Enumeration<? extends Principal> authorizedRepositories = ((Group)principal).members(); authorizedRepositories.hasMoreElements();){ Principal authorizedRepository = authorizedRepositories.nextElement(); if (StringUtils.equals(authorizedRepository.getName(), repositoryId)){ userIsAuthorizedToAccessRepository = true; break; } } if (!userIsAuthorizedToAccessRepository){ throw new CmsUnauthorizedRepositoryUseException(repositoryId); } } } } } public String login(String repositoryId, AstroboaCredentials credentials, String permanentKey, String key) { if (StringUtils.isBlank(repositoryId)){ throw new CmsException("Null or empty repository id '"+ repositoryId+"'"); } //In case configuration has changed, load any changes prior to login deployRepositoriesFoundInTheRepositoryRegistry(); CmsRepository cmsRepositoryToBeConnected = repositoryInfos.get(repositoryId); if (cmsRepositoryToBeConnected == null){ throw new CmsException("Repository with id "+ repositoryId+ " was not deployed."); } String username = credentials != null ? credentials.getUsername() : null; if (username == null) { throw new CmsLoginInvalidUsernameException("No username provided"); } if (StringUtils.isNotBlank(key) && ! RepositoryRegistry.INSTANCE.isSecretKeyValidForUser(repositoryId, username, key)) { logger.warn("User {} tried to login with invalid secret key '{}' to repository {}", new Object[]{username, key, repositoryId}); throw new CmsLoginInvalidCredentialsException(); } char[] password = credentials != null ? credentials.getPassword() : null; AstroboaCredentials credentialsToSendToJAAS = new AstroboaCredentials(username, password, cmsRepositoryToBeConnected.getIdentityStoreRepositoryId(), cmsRepositoryToBeConnected.getExternalIdentityStoreJNDIName(), repositoryId, key); //Authenticate user using JAAS application security domain //and create security context int currentAuthenticationTokenTimeout = retrieveAuthenticationTokenTimeoutForRepository(repositoryId); SecurityContext securityContext = authenticate(credentialsToSendToJAAS,repositoryId, currentAuthenticationTokenTimeout, permanentKey); //Create a new AstroboaClientContext RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryToBeConnected, securityContext); AstroboaClientContext clientContext = new AstroboaClientContext(repositoryContext, lazyLoader); AstroboaClientContextHolder.registerClientContext(clientContext, true); //Need context to be registered first addRepositoryUserIdPrincipalToSubject(securityContext.getSubject(), securityContext.getIdentity()); if (logger.isDebugEnabled()){ logger.debug("Sucessfully logged in to repository {} and register ClientContext {} to Thread {}", new Object[]{repositoryId, clientContext, Thread.currentThread().getName()}); } return securityContext.getAuthenticationToken(); } public String loginAsAnonymous(String repositoryId, String permanentKey) { return login(repositoryId, IdentityPrincipal.ANONYMOUS, null, permanentKey); } public String login(String repositoryId, String username, String key, String permanentKey) { AstroboaCredentials credentials = new AstroboaCredentials(username); return login(repositoryId, credentials, permanentKey, key); } public String loginAsAdministrator(String repositoryId, String key, String permanentKey) { String administratorUsername = RepositoryRegistry.INSTANCE.retrieveAdminUsernameForRepository(repositoryId); if (StringUtils.isBlank(administratorUsername)) { throw new CmsLoginInvalidUsernameException("No administrator username found"); } return login(repositoryId, administratorUsername, key, permanentKey); } public String login(String repositoryId, Subject subject, String permanentKey) { if (StringUtils.isBlank(repositoryId) || ! RepositoryRegistry.INSTANCE.isRepositoryRegistered(repositoryId)){ throw new CmsException("Invalid repository id "+ repositoryId); } //In case configuration has changed, load any changes prior to login deployRepositoriesFoundInTheRepositoryRegistry(); if (!repositoryInfos.containsKey(repositoryId)){ throw new CmsException("Repository id '"+repositoryId + "' found in configuration but could not be loaded"); } authorizeSubject(subject, repositoryId); CmsRepository cmsRepositoryToBeConnected = repositoryInfos.get(repositoryId); int currentAuthenticationTokenTimeout = retrieveAuthenticationTokenTimeoutForRepository(repositoryId); String authenticationToken; try { authenticationToken = createAuthenticationToken(subject, repositoryId, permanentKey); } catch (NoSuchAlgorithmException e) { throw new CmsException(e); } SecurityContext securityContext = new SecurityContext(authenticationToken, subject, currentAuthenticationTokenTimeout, getAvailableRepositoryIds()); //Create a new AstroboaClientContext RepositoryContext repositoryContext = new RepositoryContext(cmsRepositoryToBeConnected, securityContext); AstroboaClientContext clientContext = new AstroboaClientContext(repositoryContext, lazyLoader); AstroboaClientContextHolder.registerClientContext(clientContext, true); addRepositoryUserIdPrincipalToSubject(subject, securityContext.getIdentity()); if (logger.isDebugEnabled()){ logger.debug("Sucessfully logged in to repository {} and register ClientContext {} to Thread {}", new Object[]{repositoryId, clientContext, Thread.currentThread().getName()}); } return securityContext.getAuthenticationToken(); } }